.. _глава_60_start:

Инкапсуляция и защита атрибутов
================================

.. index::
    инкапсуляция
    защита атрибутов
    incapsulation
    attribute protection

Все члены всех структур и типов классов, которые мы проектировали до сих пор
были открыты для доступа извне.
Для примера рассмотрим следующую структуру описывающую студента.

.. code-block:: d

    enum Gender { female, male }
    struct Student
    {
        string name;
        Gender gender;
    }

Члены этой структуры находятся в свободном доступе для остальной части программы:

(Мы можем обращаться к членам объектов этой структуры по своему усмотрению:)

.. code-block:: d

    auto student = Student("Tim", Gender.male);
    writefln("%s is a %s student.", student.name, student.gender);

Такая свобода - удобство для программ. Например, предыдущая строка
была использована для получения следующего вывода:

.. code-block:: console

    Tim is a male student.

Однако такая свобода - это еще и ответственность. В качестве примера предположим, что, возможно.
по ошибке, имя объекта student было изменено в программе:

.. code-block:: d

    student.name = "Anna";

В качестве другого примера рассмотрим класс School. Предположим, что в этом классе есть
две переменные-члена, которые хранят номера учеников мужского и женского пола
по отдельности:

.. code-block:: d

    class School
    {
        Student[] students;
        size_t femaleCount;
        size_t maleCount;
        void add(Student student)
        {
            students ~= student;
            final switch (student.gender)
            {
            case Gender.female:
                ++femaleCount;
                break;
            case Gender.male:
                ++maleCount;
                break;
            }
        }

        override string toString() const
        {
            return format("%s female, %s male; total %s students",
                femaleCount, maleCount, students.length);
        }
    }


Функция add() добавляет студентов, обеспечивая при этом правильность подсчетов

.. code-block:: d

    auto school = new School;
    school.add(Student("Lindsey", Gender.female));
    school.add(Student("Mark", Gender.male));
    writeln(school);

Программа выдает следующий  результат:

.. code-block:: console

    1 female, 1 male; total 2 students

Однако свободный доступ к членам Школы не гарантирует.
что эта согласованность всегда будет сохраняться. Давайте рассмотрим возможность добавления нового
элемент к члену students, на этот раз напрямую:

.. code-block:: d

    school.students ~= Student("Nancy", Gender.female);

Поскольку новый ученик был добавлен в массив напрямую, без прохождения
через функцию-член add(), объект School теперь находится в противоречивом состоянии
состояние

.. code-block:: console

    1 female, 1 male; total 3 student

Инкапсуляция
-------------

Инкапсуляция - это концепция программирования, заключающаяся в ограничении доступа к элементам, чтобы
избежать проблем, подобных вышеописанной.
Еще одно преимущество инкапсуляции - отсутствие необходимости знать
детали реализации типов. В некотором смысле инкапсуляция позволяет представить
тип как "черный ящик", который используется только через его интерфейс.
Кроме того, предотвращение прямого доступа пользователей к членам позволяет
свободно изменять члены класса в будущем. До тех пор, пока функции, которые
определяющие интерфейс класса, остаются неизменными, его реализация может быть изменена
свободно.
Инкапсуляция не предназначена для ограничения доступа к конфиденциальным данным, таким как номер кредитной карты или пароль.
номер кредитной карты или пароль, и ее нельзя использовать для этой цели. Инкапсуляция - это
инструмент разработки: Она позволяет легко и безопасно использовать и кодировать типы.


Защита атрибутов
----------------

Защита атрибутов ограничивает доступ к членам структур, классов и модулей.
Существует два способа задания атрибутов защиты:
* На уровне структуры или класса, чтобы указать защиту каждого члена структуры или класса
члена по отдельности.
* На уровне модуля для указания защиты каждой функции модуля
отдельно: класс, структура, функция, перечисление и т. д.
Атрибуты защиты могут быть заданы следующими ключевыми словами. По умолчанию
атрибутом по умолчанию является public.
* public: Указывает на доступность любой части программы без каких-либо
ограничений.
Примером может служить stdout. Простой импорт std.stdio делает stdout
доступным для каждого модуля, который его импортировал.
* private: Указывает на ограниченную доступность.
К приватным членам класса и членам модуля может получить доступ только тот
модуль, который определяет этот член.
Кроме того, функции частных членов не могут быть переопределены
подклассами.
* package: Определяет доступность на уровне пакета.
Функция, отмеченная как пакетная, может быть доступна всему коду, который
является частью того же пакета. Атрибут package включает в себя только самый внутренний
самого пакета.
Например, определение пакета, находящееся внутри пакета
animal.vertebrate.cat, может быть доступно любому другому модулю пакета
vertebrate.
Как и атрибут private, функции-члены пакета не могут быть
не могут быть переопределены подклассами.
* protected: Определяет доступность для производных классов.
Этот атрибут расширяет атрибут private: К защищенному члену может
доступен не только модулю, который его определяет, но и классам, которые
наследуют от класса, определяющего этот защищенный член.
Кроме того, атрибут export определяет доступность извне
программы.

Определение
-----------

Атрибуты защиты могут быть определены тремя способами.
Если он написан перед одним определением, он определяет атрибут защиты
только этого определения. Это похоже на язык программирования Java:

 .. code-block:: d

    private int foo;
    private void bar()
    {
        // ...
    }

Если указано двоеточие, оно определяет атрибуты защиты всех
следующих определений до следующего указания атрибута защиты. Это
подобно языку программирования C++:

.. code-block:: d

    private:
    // ...
    // ... all of the definitions here are private ...
    // ...
    protected:
    // ...
    // ... all of the definitions here are protected ...
    // ..


Если он указан для блока, атрибут защиты распространяется на все определения
которые находятся внутри этого блока:

.. code-block:: d

    private
    {
    // ...
    // ... all of the definitions here are private ...
    // ...
    }

Импорт модулей по умолчанию является частным
---------------------------------------------

Модуль, импортируемый с помощью import, является частным для модуля, который его импортирует. Он
не будет виден другим модулям, которые импортируют его косвенно. Например, если
модуль school импортирует std.stdio, модули, импортирующие school, не смогут
автоматически использовать модуль std.stdio.
Атрибуты инкапсуляции и защиты
381
Предположим, что модуль school начинается со следующих строк:

.. code-block:: d

    module school.school;
    import std.stdio; // imported for this module's own use...
    // ...

Следующая программа не может быть скомпилирована, потому что writeln в ней не виден:

.. code-block:: d

    import school.school;
    void main()
    {
        writeln("hello"); // ← ошибка компиляции
    }

std.stdio также должен быть импортирован этим модулем:

.. code-block:: d

    import school.school;
    import std.stdio;

    void main()
    {
        writeln("hello"); // now compiles
    }

Иногда бывает необходимо, чтобы один модуль косвенно представлял другие модули. Например,
например, имеет смысл, чтобы модуль школы автоматически импортировал модуль
модуль student для своих пользователей. Это достигается путем пометки импорта как публичного:

.. code-block:: d

    module school.school;
    public import school.student;
    // ...

С помощью этого определения модули, импортирующие school, могут использовать определения, которые находятся
внутри модуля student без необходимости его импортировать:

.. code-block:: d

    import school.school;

    void main()
    {
        auto student = Student("Tim", Gender.male);
        // ...
    }

Хотя приведенная выше программа импортирует только модуль школы, структура
student.Student struct также доступен для нее


Когда использовать инкапсуляцию
--------------------------------

Инкапсуляция позволяет избежать проблем, подобных той, которую мы рассмотрели в
вводном разделе этой главы. Это бесценный инструмент, позволяющий гарантировать, что объекты
всегда находились в согласованных состояниях. Инкапсуляция помогает сохранить инварианты структур и классов
инварианты, защищая члены от прямых модификаций со стороны пользователей типа.
Инкапсуляция обеспечивает свободу реализации, абстрагируя
абстрагируя реализацию от пользовательского кода. В противном случае, если бы пользователи имели прямой доступ, например, к
например, School.students, было бы сложно изменить дизайн класса, изменив массив, например, на
изменив этот массив, например, на ассоциативный, потому что это повлияло бы на весь пользовательский
код, который обращался к этому члену.
Инкапсуляция - одно из самых мощных преимуществ объектно-ориентированного
программирования.

Пример
------

Давайте определим структуру Student и класс School, используя преимущества
инкапсуляции и используем их в короткой тестовой программе.
Этот пример программы будет состоять из трех файлов. Как вы помните из
предыдущей главы, два из этих файлов, являясь частями пакета school, будут находиться
в каталоге "school": ∙ "school/student.d": Модуль student, определяющий структуру Student
∙"school/school.d": Модуль школы, определяющий класс School
∙"deneme.d": Короткая тестовая программа
Вот файл "school/student.d":

.. code-block:: d

    module school.student;

    import std.string;
    import std.conv;

    enum Gender
    {
        female,
        male
    }

    struct Student
    {
        package string name;
        package Gender gender;

        string toString() const
        {
            return format("%s is a %s student.", name, to!string(gender));
        }
    }


Члены этой структуры помечены как пакетные, чтобы обеспечить доступ только для
модулям того же пакета. Вскоре мы увидим, что школа будет обращаться к
к этим членам напрямую. (Обратите внимание, что даже это следует рассматривать как нарушение
принцип инкапсуляции. Тем не менее, давайте придерживаться атрибута package в этом примере программы.)
Ниже приведен модуль "school/school.d", который использует предыдущий:


.. code-block:: d

    module school.school;

    public import school.student; // 1
    import std.string;

    class School
    {
    private: // 2
        Student[] students;
        size_t femaleCount;
        size_t maleCount;
    public: // 3
        void add(Student student)
        {
            students ~= student;
            final switch (student.gender)
            { // 4a
            case Gender.female:
                ++femaleCount;
                break;
            case Gender.male:
                ++maleCount;
                break;
            }
        }

        override string toString() const
        {
            string result = format("%s female, %s male; total %s students", femaleCount, maleCount, students
                    .length);
            foreach (i, student; students)
            {
                result ~= (i == 0) ? ": " : ", ";
                result ~= student.name; // 4b
            }
            return result;
        }
    }


#. school.student импортируется публично, чтобы пользователям
school.school не нужно будет импортировать этот модуль явно. В некотором смысле
модуль student становится доступным благодаря модулю school.
#. Все переменные-члены модуля School помечены как частные. Это важно для защиты согласованности переменных-членов этого
класса.
#. Чтобы этот класс был полезен, в нем должны быть представлены некоторые функции-члены. add() и
toString() доступны пользователям этого класса.
#. Поскольку две переменные-члены Student были помечены как пакетные, будучи
будучи частью того же пакета, School может получить доступ к этим переменным.
Наконец, ниже приведена тестовая программа, использующая эти типы:

.. code-block:: d

    import std.stdio;
    import school.school;

    void main()
    {
        auto student = Student("Tim", Gender.male);
        writeln(student);
        auto school = new School;
        school.add(Student("Lindsey", Gender.female));
        school.add(Student("Mark", Gender.male));
        school.add(Student("Nancy", Gender.female));
        writeln(school);
    }

Эта программа может использовать Student и School только через их публичные интерфейсы.
Она не может получить доступ к переменным-членам этих типов. В результате объекты
всегда будут соответствовать друг другу:

.. code-block:: console

    Tim is a male student.
    2 female, 1 male; total 3 students: Lindsey, Mark, Nancy

Обратите внимание, что программа взаимодействует со школой только с помощью функций add() и toString()
функции. До тех пор, пока интерфейсы этих функций остаются неизменными, изменения
в их реализациях не повлияют на работу программы, приведенной выше.
